package ch6;

class Main {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		System.out.println(new Topping(
				new Anchovy(),
				new Topping(
						Integer.valueOf(3),
						new Topping(new Zero(),
								new Bottom()))).remove(new RemoveV(), 
										new Zero()));

		System.out.println(new Topping(
				Integer.valueOf(3),
				new Topping(Integer.valueOf(2),
						new Topping(Integer.valueOf(3),
								new Bottom()))).substitute(
										new SubstituteV(),
										Integer.valueOf(5),
										Integer.valueOf(3)));
	}
}

//----------------------------------------------------

class RemoveV {
	PizzaPie forBottom(Object i) {
		return new Bottom();
	}

	PizzaPie forTopping(Object o, Object t, PizzaPie r) {
		if (o.equals(t)) {
			return r.remove(this, o);
		} else {
			return new Topping(t, r.remove(this, o));
		}
	}
}

//----------------------------------------------------

class Bottom extends PizzaPie {

	@Override
	public String toString() {
		return "new Bottom()";
	}

	@Override
	PizzaPie remove(RemoveV removeFn, Object o) {
		return removeFn.forBottom(o);
	}

	@Override
	PizzaPie substitute(SubstituteV substituteFn,
			Object oNew, Object oOld) {
		return substituteFn.forBottom(oNew, oOld);
	}
}

//----------------------------------------------------

class Topping extends PizzaPie {
	@Override
	public String toString() {
		return "new Topping(t=" + t + ", r=" + r + ")";
	}

	Object t;
	PizzaPie r;
	
	Topping(Object _t, PizzaPie _r) {
		t = _t;
		r = _r;
	}

	@Override
	PizzaPie remove(RemoveV removeFn, Object o) {
		return removeFn.forTopping(o, t, r);
	}

	@Override
	PizzaPie substitute(SubstituteV substituteFn,
			Object oNew, Object oOld) {
		return substituteFn.forTopping(t, r, oNew, oOld);
	}
}

//----------------------------------------------------

class SubstituteV {
	PizzaPie forBottom(Object oNew, Object oOld) {
		return new Bottom();
	}

	PizzaPie forTopping(Object t, PizzaPie r,
			Object oNew, Object oOld) {
		if (oOld.equals(t)) {
			return new Topping(oNew, r.substitute(
					this, oNew, oOld));
		} else {
			return new Topping(t, r.substitute(
					this, oNew, oOld));
		}
	}
}

//----------------------------------------------------

public abstract class PizzaPie {
	abstract PizzaPie remove(RemoveV r,
			Object o);
	abstract PizzaPie substitute(SubstituteV s,
			Object oNew,
			Object oOld);
}

//----------------------------------------------------

abstract class Fish {
	
}

//----------------------------------------------------

class Anchovy extends Fish {
	@Override
	public String toString() {
		return "new Anchovy()";
	}

	public boolean equals(Object o) {
		return (o instanceof Anchovy);
	}
}

//----------------------------------------------------

abstract class NumD {
	
}

class Zero extends NumD {
	public String toString() {
		return "new " + getClass().getName();
	}

	@Override
	public boolean equals(Object o) {
		return (o instanceof Zero);
	}
}

class OneMoreThan extends NumD {
	@Override
	public String toString() {
		return "new OneMoreThan(predecessor=" + predecessor + ")";
	}

	NumD predecessor;
	
	OneMoreThan(NumD _p) {
		predecessor = _p;
	}

	@Override
	public boolean equals(Object o) {
		if (o instanceof OneMoreThan) {
			return predecessor.equals(
					((OneMoreThan)o).predecessor);
		} else {
			return false;
		}
	}
}

//----------------------------------------------------
